> Tool Use and Function Calling
Tool Use and Function Calling
πΏ Budding note β extending agent capabilities through tools.
What is Tool Use?
Tool use enables agents to interact with external systems beyond their training data:
- APIs: REST, GraphQL, gRPC
- Databases: SQL, vector stores, key-value
- File systems: Read/write files
- Computation: Code execution, calculators
- External services: Email, Slack, payment processors
Related: AI Agents Fundamentals for agent architectures
Function Calling vs Tool Use
Function calling (API-native):
# LLM returns structured function call
{
"function": "get_weather",
"arguments": {
"location": "Tokyo",
"unit": "celsius"
}
}
Tool use (framework abstraction):
# Framework handles execution
tools = [weather_tool, calculator_tool]
agent = Agent(llm=llm, tools=tools)
result = agent.run("What's the weather in Tokyo?")
Claude Tool Use
Claude has native tool use support:
from anthropic import Anthropic
client = Anthropic()
# Define tools
tools = [
{
"name": "get_weather",
"description": "Get current weather for a location",
"input_schema": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "City name, e.g. Tokyo"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Temperature unit"
}
},
"required": ["location"]
}
}
]
# Agent loop with tool use
messages = [{"role": "user", "content": "What's the weather in Tokyo?"}]
response = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=1024,
tools=tools,
messages=messages
)
# Check if tool was called
if response.stop_reason == "tool_use":
tool_use_block = next(
block for block in response.content
if block.type == "tool_use"
)
# Execute tool
if tool_use_block.name == "get_weather":
weather_data = get_weather_api(
location=tool_use_block.input["location"],
unit=tool_use_block.input.get("unit", "celsius")
)
# Continue conversation with result
messages.append({"role": "assistant", "content": response.content})
messages.append({
"role": "user",
"content": [{
"type": "tool_result",
"tool_use_id": tool_use_block.id,
"content": str(weather_data)
}]
})
# Get final response
final_response = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=1024,
tools=tools,
messages=messages
)
Related: Claude Agent Patterns for Claude-specific patterns
Tool Definition Best Practices
1. Clear Descriptions
# β Bad: Vague description
{
"name": "search",
"description": "Searches stuff",
"input_schema": {...}
}
# β
Good: Specific and actionable
{
"name": "web_search",
"description": "Search the web for current information. Use this when you need recent data not in your training (news, weather, stock prices, etc.)",
"input_schema": {...}
}
2. Detailed Parameter Schemas
{
"name": "send_email",
"description": "Send an email to a recipient",
"input_schema": {
"type": "object",
"properties": {
"to": {
"type": "string",
"description": "Recipient email address (must be valid format)"
},
"subject": {
"type": "string",
"description": "Email subject line (max 100 characters)"
},
"body": {
"type": "string",
"description": "Email body content (plain text or HTML)"
},
"priority": {
"type": "string",
"enum": ["low", "normal", "high"],
"description": "Email priority level",
"default": "normal"
}
},
"required": ["to", "subject", "body"]
}
}
3. Include Examples
{
"name": "database_query",
"description": "Execute SQL query on the database. Examples: 'SELECT * FROM users WHERE age > 18', 'SELECT COUNT(*) FROM orders WHERE status = \"completed\"'",
"input_schema": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "SQL SELECT query (read-only)"
}
},
"required": ["query"]
}
}
Common Tool Patterns
1. Web Search
import requests
def web_search(query: str, num_results: int = 5) -> list[dict]:
"""Search web using DuckDuckGo API"""
response = requests.get(
"https://api.duckduckgo.com/",
params={
"q": query,
"format": "json",
"no_html": 1
}
)
data = response.json()
return [
{
"title": result.get("Title"),
"snippet": result.get("Text"),
"url": result.get("FirstURL")
}
for result in data.get("RelatedTopics", [])[:num_results]
]
# Tool definition
web_search_tool = {
"name": "web_search",
"description": "Search the web for current information",
"input_schema": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "Search query"},
"num_results": {"type": "integer", "description": "Number of results (default 5)"}
},
"required": ["query"]
}
}
2. Database Access
import sqlite3
from typing import List, Dict, Any
class DatabaseTool:
def __init__(self, db_path: str):
self.db_path = db_path
def query(self, sql: str) -> List[Dict[str, Any]]:
"""Execute read-only SQL query"""
# Security: Only allow SELECT
if not sql.strip().upper().startswith("SELECT"):
raise ValueError("Only SELECT queries allowed")
conn = sqlite3.connect(self.db_path)
conn.row_factory = sqlite3.Row
cursor = conn.cursor()
try:
cursor.execute(sql)
rows = cursor.fetchall()
return [dict(row) for row in rows]
finally:
conn.close()
db_tool = DatabaseTool("./data.db")
# Tool definition
{
"name": "query_database",
"description": "Query the company database. Use for customer data, orders, products, etc.",
"input_schema": {
"type": "object",
"properties": {
"sql": {
"type": "string",
"description": "SQL SELECT query (read-only)"
}
},
"required": ["sql"]
}
}
Related: Agent Security Considerations for database safety
3. File Operations
import os
from pathlib import Path
class FileSystemTool:
def __init__(self, allowed_dir: str):
self.allowed_dir = Path(allowed_dir).resolve()
def read_file(self, path: str) -> str:
"""Read file content"""
file_path = (self.allowed_dir / path).resolve()
# Security: Prevent path traversal
if not str(file_path).startswith(str(self.allowed_dir)):
raise ValueError("Access denied: Path outside allowed directory")
return file_path.read_text()
def write_file(self, path: str, content: str) -> str:
"""Write content to file"""
file_path = (self.allowed_dir / path).resolve()
if not str(file_path).startswith(str(self.allowed_dir)):
raise ValueError("Access denied: Path outside allowed directory")
file_path.parent.mkdir(parents=True, exist_ok=True)
file_path.write_text(content)
return f"Wrote {len(content)} characters to {path}"
def list_files(self, directory: str = ".") -> List[str]:
"""List files in directory"""
dir_path = (self.allowed_dir / directory).resolve()
if not str(dir_path).startswith(str(self.allowed_dir)):
raise ValueError("Access denied: Path outside allowed directory")
return [f.name for f in dir_path.iterdir()]
4. Code Execution
import subprocess
import tempfile
from typing import Dict
def execute_python(code: str, timeout: int = 5) -> Dict[str, str]:
"""Execute Python code in sandbox"""
# Write code to temp file
with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
f.write(code)
temp_path = f.name
try:
# Execute with timeout
result = subprocess.run(
['python3', temp_path],
capture_output=True,
text=True,
timeout=timeout
)
return {
"stdout": result.stdout,
"stderr": result.stderr,
"returncode": result.returncode
}
except subprocess.TimeoutExpired:
return {
"stdout": "",
"stderr": f"Execution timed out after {timeout}s",
"returncode": -1
}
finally:
os.unlink(temp_path)
# Tool definition
{
"name": "execute_python",
"description": "Execute Python code and return output. Use for calculations, data processing, etc.",
"input_schema": {
"type": "object",
"properties": {
"code": {
"type": "string",
"description": "Python code to execute"
}
},
"required": ["code"]
}
}
β οΈ Security warning: See Agent Security Considerations for safe code execution
Multi-Step Tool Use
Agents often need multiple tool calls:
def multi_step_agent(task: str):
"""Agent that chains tool calls"""
messages = [{"role": "user", "content": task}]
max_iterations = 10
for i in range(max_iterations):
response = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=4096,
tools=tools,
messages=messages
)
# Add assistant response
messages.append({"role": "assistant", "content": response.content})
# Check stop reason
if response.stop_reason == "end_turn":
# Task complete
return extract_final_answer(response)
elif response.stop_reason == "tool_use":
# Execute all tool calls in this turn
tool_results = []
for block in response.content:
if block.type == "tool_use":
result = execute_tool(block.name, block.input)
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": str(result)
})
# Add tool results
messages.append({"role": "user", "content": tool_results})
elif response.stop_reason == "max_tokens":
raise Exception("Response truncated - increase max_tokens")
raise Exception("Max iterations reached without completion")
# Example: Multi-step research task
result = multi_step_agent(
"Find the current CEO of Microsoft and their age, then calculate how many days they've been CEO"
)
# Agent will:
# 1. web_search("Microsoft CEO")
# 2. web_search("Satya Nadella age")
# 3. calculator("days since February 4, 2014")
Parallel Tool Execution
Some frameworks support parallel tool calls:
async def parallel_tool_execution(tool_calls: list):
"""Execute multiple tools concurrently"""
import asyncio
async def execute_single(tool_call):
tool_name = tool_call["name"]
tool_args = tool_call["arguments"]
# Execute tool
result = await tools[tool_name](**tool_args)
return {
"tool_call_id": tool_call["id"],
"result": result
}
# Execute all in parallel
results = await asyncio.gather(
*[execute_single(tc) for tc in tool_calls]
)
return results
# Example: Research multiple topics simultaneously
tool_calls = [
{"id": "1", "name": "web_search", "arguments": {"query": "AI agents"}},
{"id": "2", "name": "web_search", "arguments": {"query": "LangChain"}},
{"id": "3", "name": "web_search", "arguments": {"query": "AutoGPT"}}
]
results = await parallel_tool_execution(tool_calls)
Error Handling
Tools can fail - handle gracefully:
def safe_tool_execution(tool_name: str, tool_args: dict) -> dict:
"""Execute tool with error handling"""
try:
# Validate arguments
validate_tool_args(tool_name, tool_args)
# Execute tool
result = tools[tool_name](**tool_args)
return {
"success": True,
"result": result
}
except ValidationError as e:
return {
"success": False,
"error": f"Invalid arguments: {e}",
"suggestion": "Check the tool schema and try again"
}
except TimeoutError:
return {
"success": False,
"error": "Tool execution timed out",
"suggestion": "Try with different parameters or use a different tool"
}
except Exception as e:
return {
"success": False,
"error": f"Tool execution failed: {e}",
"suggestion": "Check if the tool is available and try again"
}
# Agent receives structured error
messages.append({
"role": "user",
"content": [{
"type": "tool_result",
"tool_use_id": tool_id,
"content": json.dumps(safe_tool_execution(name, args)),
"is_error": not result["success"]
}]
})
Tool Discovery
Help agents understand available tools:
def list_available_tools() -> str:
"""Generate tool documentation for agent"""
doc = "Available tools:\n\n"
for tool in tools:
doc += f"**{tool['name']}**\n"
doc += f"Description: {tool['description']}\n"
doc += f"Parameters:\n"
for param, schema in tool['input_schema']['properties'].items():
required = "required" if param in tool['input_schema'].get('required', []) else "optional"
doc += f" - {param} ({schema['type']}, {required}): {schema.get('description', '')}\n"
doc += "\n"
return doc
# Add to system prompt
system_prompt = f"""You are a helpful agent. You have access to these tools:
{list_available_tools()}
Use tools when needed to complete tasks."""
Tool Composability
Build complex tools from simple ones:
class ComposableTool:
def __init__(self, name: str, func: Callable):
self.name = name
self.func = func
def __call__(self, *args, **kwargs):
return self.func(*args, **kwargs)
def __or__(self, other):
"""Compose tools with | operator"""
def composed(*args, **kwargs):
result1 = self(*args, **kwargs)
return other(result1)
return ComposableTool(
name=f"{self.name}_then_{other.name}",
func=composed
)
# Define simple tools
search = ComposableTool("search", lambda q: web_search(q))
summarize = ComposableTool("summarize", lambda text: llm_summarize(text))
translate = ComposableTool("translate", lambda text: llm_translate(text, "es"))
# Compose into pipeline
research_pipeline = search | summarize | translate
# Use composed tool
result = research_pipeline("AI agents")
# Searches β Summarizes β Translates to Spanish
Caching Tool Results
Avoid redundant API calls:
from functools import lru_cache
import hashlib
import json
class CachedTool:
def __init__(self, func: Callable, ttl_seconds: int = 3600):
self.func = func
self.cache = {}
self.ttl = ttl_seconds
def __call__(self, **kwargs):
# Create cache key from arguments
cache_key = hashlib.md5(
json.dumps(kwargs, sort_keys=True).encode()
).hexdigest()
# Check cache
if cache_key in self.cache:
cached_at, result = self.cache[cache_key]
if time.time() - cached_at < self.ttl:
return result
# Execute and cache
result = self.func(**kwargs)
self.cache[cache_key] = (time.time(), result)
return result
# Wrap expensive tools
web_search = CachedTool(web_search_api, ttl_seconds=1800) # 30 min cache
Tool Monitoring
Track tool usage and performance:
class MonitoredTool:
def __init__(self, name: str, func: Callable):
self.name = name
self.func = func
self.call_count = 0
self.total_duration = 0
self.errors = 0
def __call__(self, **kwargs):
self.call_count += 1
start = time.time()
try:
result = self.func(**kwargs)
duration = time.time() - start
self.total_duration += duration
logger.info(f"Tool {self.name} completed in {duration:.2f}s")
return result
except Exception as e:
self.errors += 1
logger.error(f"Tool {self.name} failed: {e}")
raise
def stats(self) -> dict:
return {
"name": self.name,
"calls": self.call_count,
"errors": self.errors,
"avg_duration": self.total_duration / self.call_count if self.call_count > 0 else 0,
"success_rate": (self.call_count - self.errors) / self.call_count if self.call_count > 0 else 0
}
Related: Agent Evaluation & Testing
Connection Points
Prerequisites:
- AI Agents Fundamentals β Agent architectures
- Agent Frameworks Comparison β Framework tool APIs
Related:
- Claude Agent Patterns β Claude tool use patterns
- Agent Security Considerations β Tool safety
- Building Agents with LangChain β LangChain tools
Advanced:
- Production Agent Deployment β Production tool management
- Agent Evaluation & Testing β Testing tool reliability